#!/usr/bin/python3
# Copyright 2020 Timothy Trippel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys

import cocotb
from cocotb.triggers import RisingEdge

# Import custom cocotb extension packages
sys.path.append("/Users/ttrippel/repos/hw-fuzzing/hw/tb")
from cocotb_ext.tlul_fuzz_harness import HWFuzzOpcode, TLULFuzzHarness, bin2hex

CLK_PERIOD_NS = 10  # duration of simulation clock period
DUT_RESET_DURATION_NS = 50  # duration to hold DUT in reset for in ns
TIMEOUT = 5000000  # number of fuzzing transactions to do before killing test


@cocotb.test()
async def rv_timer_tb(dut):
  """Reads TL-UL transactions from STDIN (generated by AFL) to fuzz the DUT.

  Args:
    dut: The object representing the DUT being simulated.

  Returns:
    None

  Raises:
    AssertionError: ...
  """
  # Instantiate the TL-UL testbench
  tb = TLULFuzzHarness(dut, CLK_PERIOD_NS, debug=False)

  # Reset the DUT
  await tb.reset(DUT_RESET_DURATION_NS)

  # Send in AFL generated transactions
  dut._log.info("Running fuzz test...")
  timeout = TIMEOUT
  fuzzer_opcode = tb.get_fuzzer_opcode()
  while fuzzer_opcode is not None and timeout > 0:

    # Wait one clock cycle
    if fuzzer_opcode == HWFuzzOpcode.wait:
      dut._log.info("(wait)")
      await RisingEdge(dut.clk_i)

    # TL-UL Get
    elif fuzzer_opcode == HWFuzzOpcode.read:
      tlul_address = tb.get_tlul_address()
      if tlul_address is not None:
        value = await tb.tlul.get(tlul_address)
        dut._log.info("(read  -- addr: data) 0x{:0>8X}: {} (32b{})".format(
            tlul_address, bin2hex(value), value.binstr))
      else:
        dut._log.info("Ran out of fuzzer input bytes... ending test.")
        break

    # TL-UL PutFullData
    else:
      tlul_address = tb.get_tlul_address()
      tlul_data = tb.get_tlul_data()
      if tlul_address is not None and tlul_data is not None:
        await tb.tlul.put_full(tlul_address, tlul_data)
        dut._log.info(
            "(write -- addr: data) 0x{:0>8X}: {:0>8X} (32b{:0>32b})".format(
                tlul_address, tlul_data, tlul_data))
      else:
        dut._log.info("Ran out of fuzzer input bytes... ending test.")
        break

    # Get next opcode and decrement timeout
    fuzzer_opcode = tb.get_fuzzer_opcode()
    timeout -= 1

  # Check if timeout triggered test completion
  if timeout == 0:
    dut._log.warning("Test terminated early to to timeout.")
  dut._log.info("Fuzz test complete.")
